#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""
A static set of resource caches for
caching environmentally-bound resources.
"""

namespace Grawlix.Services.Resources
import Grawlix.Services
import C5
import System
import System.Threading

transient final class ResourceCache[of
	T/*(interface, IDynamicResource)*/](
	PermanentMarshalByRefObject):
"""Type-based caching for environment-based resources."""
	
	# FIXME: Made public to suppress warning. Do not instantiate manually.
	/*private */def constructor(
		repository as ResourceRepository[of T]):
	"""
	Initializes the cache.
	Param repository: The base repository to use.
	"""
		# FIXME: Cast needed for bug in Boo generics
		_current = self as ResourceCache[of T]
		_repository = repository
		_creator = _repository.Creator
		if _creator is null:
			raise InvalidOperationException(
				"The AbstractResourceProxyCreator<${typeof(T).Name}> " +
				"given to ${self} is null.")
	
	private def TryGetResource(identifier as Uri):
	"""
	Returns the specified resource or null.
	Param identifier: The resource to find.
	Returns: The resource if found, otherwise null.
	"""
		if _cache.Contains(identifier):
			target = _cache[identifier].Target
			typedTarget = target as AbstractResourceProxy[of T]
			assert target is typedTarget
			return typedTarget
		return null
	
	def MarshalResource([required] identifier as Uri):
	"""
	Pulls a resource from the cache or
	requests one from the repository.
	Param identifier: The URI of the resource.
	Returns: The marshalled resource.
	"""
		lock _cache:
			resource = TryGetResource(identifier)
			return resource unless resource is null
			## FIXME: Cast is needed temporarily
			resource = _creator.Create(identifier)
			if resource is null:
				raise InvalidOperationException(
					"The AbstractResourceProxyCreator<${typeof(T).Name}> " +
					"provided to ${self} does not " +
					"create objects of type " +
					"AbstractResourceProxy<${typeof(T).Name}>.")
			_cache[identifier] = WeakReference(resource)
			# FIXME: Cast needed for bug in Boo generics
			resource.Attach(self as ResourceCache[of T])
		# Dance a little to keep lock order correct
		_repository.RequestUpdate(
			AppDomain.CurrentDomain,
			# FIXME: Cast needed for bug in Boo generics
			self as ResourceCache[of T],
			identifier)
		return resource
	
	[lock(_cache)]
	def UpdateState(
		[required] identifier as Uri,
		[required] state as ResourceState):
	"""
	Updates the state on the specified resource.
	Param identifier: The URI of the resource to update.
	Param state: The state to update.
	"""
		resource = TryGetResource(identifier)
		unless resource is null:
			resource.SetState(state)
	
	def RemoveResource(
		[required] resource as AbstractResourceProxy[of T]):
	"""
	Removes the specified resource.
	Param resource: The finalized resource being removed.
	"""
		identifier = resource.Identifier
		lock _cache:
			assert _cache.Contains(identifier)
			data = _cache[identifier].Target
			# During closing, data can be resource
			return unless data is null or data is resource
			_cache.Remove(identifier)
		# Dance a little to keep lock order correct
		_repository.RequestRemoval(AppDomain.CurrentDomain, identifier)
	
	[lock(_cache)]
	def ResourcePresent([required] identifier as Uri):
	"""
	Checks to see if a resource is present.
	Param identifier: The identifier of the resource.
	Returns: true if the resource is present, else false.
	"""
		return _cache.Contains(identifier)
	
	# FIXME: Boo bug requires this be not internal
	/*internal */static def FindCurrentCache(
		repository as ResourceRepository[of T]):
	"""
	Retrieves or creates the resource cache.
	Param repository: The base repository in the hierarchy.
	Returns: The marshalled resource.
	"""
		# Double-checked locking
		if _current is null:
			lock _accessLock:
				if _current is null:
					current = ResourceCache[of T](repository)
					Thread.MemoryBarrier()
					# The memory barrier is present so the fields get
					# initialized before the cache is published
					# Lock acquisitions/releases are implicit barriers
					_current = current
		if _current.Repository is not repository:
			raise InvalidOperationException(
				"Attempt to use a resource repository for " +
				"${typeof(T).Name} resources which conflicts with " +
				"a different repository in " +
				"the current AppDomain " +
				"(${AppDomain.CurrentDomain.FriendlyName}). " +
				"Ensure that only one resource factory is " +
				"active per resource type.")
		return _current
	
	[getter(Repository)]
	private _repository as ResourceRepository[of T]
	"""The backing repository for the resource."""
	
	private _creator as AbstractResourceProxyCreator[of T]
	"""The resource creator object."""
	
	private _cache = HashDictionary[of Uri, WeakReference]()
	"""The data stored within the cache."""
	
	private static _current as ResourceCache[of T]
	"""The current active cache."""
	
	private static _accessLock = object()
	"""Provides mutual exclusion for static variables."""
